/*
Copyright (c) 2010, Daniele Simonetti
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "playlist.h"
#include "controller.h"
#include "utility.h"

#include "player/playlistentry.h"
#include "player/playlistfactory.h"

#include <QMimeData>
#include <QFileInfo>
#include <QTime>
#include <QtConcurrentRun>
#include <QDebug>

PlayList::PlayList(QObject *parent) :
    QAbstractListModel(parent), _writer(NULL), _sortOrder(Qt::AscendingOrder)
{

}

PlayList::~PlayList()
{
    foreach(PlaylistEntry* entry, _entries)
    {
        if ( entry != NULL )
        {
            delete entry;
            entry = NULL;
        }
    }
    _entries.clear();
    delete _writer;
}

void PlayList::addFile(QString file, int beginRow)
{
    QFileInfo info(file);
    if ( info.fileName().startsWith(".") )
        return;

    if ( info.isDir() )
    {
        QDir folder(file);
        addFiles(QDir(file).entryList(), folder);
        return;
    }

    if ( beginRow < 0 )
        beginRow = rowCount(QModelIndex());

    PlaylistEntry* entry = new PlaylistEntry(file);
    if ( entry->isValid() )
    {
        insertRows(beginRow, 1, QModelIndex());
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, entry);
    }
    else
        delete entry;
}

void PlayList::addFiles(QStringList files, QDir baseDir, int beginRow)
{
    foreach (QString file, files)
    {
        if ( beginRow > 0 )
            addFile(baseDir.filePath(file), beginRow++);
        else
            addFile(baseDir.filePath(file));
    }
}

void PlayList::addURIs(QList<QUrl> urls, int beginRow)
{
    foreach (QUrl url, urls)
    {
        if ( beginRow > 0 )
            addURI(url, beginRow++);
        else
            addURI(url);
    }
}

void PlayList::addURI(QUrl uri, int beginRow)
{
    QFileInfo info(uri.toLocalFile());
    if ( info.fileName().startsWith(".") )
        return;

    if ( info.isDir() )
    {
        QDir folder(uri.toLocalFile());
        addFiles(folder.entryList(), folder);
        return;
    }

    if ( beginRow < 0 )
        beginRow = rowCount(QModelIndex());

    PlaylistEntry* entry = new PlaylistEntry(uri);
    if ( entry->isValid() )
    {
        insertRows(beginRow, 1, QModelIndex());
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, entry);
    }
    else
        delete entry;
}

void PlayList::addPlaylist(IPlaylistReader* pls)
{
    this->clear();
    if ( pls == NULL )
        return;
    _entries = pls->load();
    emit dataChanged(index(0, 0), index(_entries.count()-1, 0));
}

int PlayList::rowCount( const QModelIndex & parent ) const
{
    Q_UNUSED(parent);
    if (parent.isValid())
        return 0;
    else
        return _entries.count();
}

QVariant PlayList::data ( const QModelIndex & index, int role ) const
{
    if (!index.isValid())
        return QVariant::Invalid;

    if (index.row() >= _entries.count() || index.row() < 0)
        return QVariant::Invalid;

    PlaylistEntry* e = _entries.at(index.row());
    if ( e == NULL )
        return QVariant::Invalid;

    if (role == Qt::DisplayRole)
    {
        bool display_row_number = Controller::settings()->value("display_row_number", false).toBool();
        if ( display_row_number )
        {
            QString max_idx; max_idx.setNum(_entries.size());
            return QString("%1\t%2").arg(index.row()+1, max_idx.size()).arg(e->toString());
        }
        return e->toString();
    }
    else if ( role == Qt::ToolTipRole)
    {
        QTime len = QTime().addSecs(e->len());
        double mb = ((double)e->filesize() / (1024*1024) );
        QString tip = QString(tr("Artist: %1\nAlbum: %2\nGenre: %3\nTrack: %4\nLength: %5\nBitrate: %6Kbps\nSize: %7MB"))
                      .arg(e->artist())
                      .arg(e->album())
                      .arg(e->genre())
                      .arg(e->track())
                      .arg(len.toString("m:ss"))
                      .arg(e->bitrate())
                      .arg(mb, 0, 'f', 2);
        return tip;
    }

    return QVariant::Invalid;
}

bool PlayList::insertRows ( int row, int count, const QModelIndex & parent )
{
    beginInsertRows(parent, row, row+count-1);

    for (int i=0; i < count; ++i)
    {
        _entries.insert(i+row, NULL);
    }

    endInsertRows();
    return true;
}

Qt::DropActions PlayList::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
}

Qt::ItemFlags PlayList::flags(const QModelIndex &index) const
{
    if (index.isValid())
        return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);

    return Qt::ItemIsDropEnabled;
}

bool PlayList::removeRows ( int row, int count, const QModelIndex & parent )
{
    int beginRow = qMax(0, row);
    int endRow = qMin(row + count - 1, _entries.count() - 1);
    beginRemoveRows(parent, beginRow, endRow);

    for(int i = beginRow; i <= endRow; ++i )
    {
        PlaylistEntry* e = _entries.at(i);
        delete e;
        e = NULL;
        _entries.removeAt(beginRow);
    }

    endRemoveRows();
    return true;
}

bool PlayList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    Q_UNUSED(parent);

    if (action == Qt::IgnoreAction)
        return true;

    if (column > 0)
        return false;

    int beginRow;

    if (row != -1)
        beginRow = row;
    else
        beginRow = rowCount(QModelIndex());

    if (data->hasUrls() && action == Qt::CopyAction)
    {
        QtConcurrent::run(this, &PlayList::addURIs, data->urls(), beginRow);
        return true;
    }
    else if ( data->hasFormat("application/x-playlistentryidx"))
    {
        QByteArray encodedData = data->data("application/x-playlistentryidx");
        int idx = 0;
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        while(!stream.atEnd())
        {
            stream >> idx;
            _entries.move(idx, beginRow);
        }
    }

    return false;
}

QStringList PlayList::mimeTypes() const
{
    QStringList types;
    types << "application/x-playlistentryidx";
    return types;
}

QMimeData *PlayList::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    foreach (QModelIndex index, indexes)
    {
        if (index.isValid())
            stream << index.row();
    }

    mimeData->setData("application/x-playlistentryidx", encodedData);
    return mimeData;
}

bool PlayList::setData( const QModelIndex & index, const QVariant & value, int role )
{
    if (index.isValid() && role == Qt::EditRole)
    {
        int row = index.row();
        _entries[row] = value.value<PlaylistEntry*>();

        emit dataChanged(index, index);
        return true;
    }

    return false;
}

bool PlayList::setData( const QModelIndex & index, PlaylistEntry* value, int role )
{
    if (index.isValid() && role == Qt::EditRole)
    {
        int row = index.row();
        _entries[row] = value;

        emit dataChanged(index, index);
        return true;
    }

    return false;
}

PlaylistEntry* PlayList::at(const QModelIndex & index) const
{
    if ( !index.isValid() )
        return NULL;
    //QVariant qv = _entries.at(index.row());
    if ( _entries.count() > index.row() )
        return _entries.at(index.row());
    return NULL;
}

const QModelIndex & PlayList::currentIndex() const
{
    return _current;
}

void PlayList::setCurrentIndex(QModelIndex & index)
{
    _current = index;
}

PlaylistEntry* PlayList::current() const
{
    if ( !_current.isValid() )
        return NULL;
    return at(_current);
}

PlaylistEntry* PlayList::next()
{
    if ( !_current.isValid() )
    {
        _current = index(0, 0, QModelIndex());
    }
    else
    {
        switch(_playMode)
        {
        case NoRepeat:
            _current = _current.sibling(_current.row()+1, 0);
            break;
        case RepeatAll:
            _current = _current.sibling(_current.row()+1, 0);
            if ( !_current.isValid() )
                _current = index(0, 0, QModelIndex());
            break;
        case RepeatOne:
            break;
        case Shuffle:
        {
            do
            {
                int n = util::randrange(_entries.count());
                _current = _current.sibling(n, 0);
            }
            while(!_current.isValid());
        }
        break;
        }
    }

    if ( !_current.isValid() )
        return NULL;
    return at(_current);
}

PlaylistEntry* PlayList::prev()
{
    if ( !_current.isValid() )
    {
        _current = index(0, 0, QModelIndex());
    }
    else
    {
        switch(_playMode)
        {
        case NoRepeat:
            _current = _current.sibling(_current.row()-1, 0);
            break;
        case RepeatAll:
            _current = _current.sibling(_current.row()-1, 0);
            if ( !_current.isValid() )
                _current = index(0, 0, QModelIndex());
            break;
        case RepeatOne:
            break;
        case Shuffle:
            return next();
            break;
        }
    }

    if ( !_current.isValid() )
        return NULL;
    return at(_current);
}

QModelIndex PlayList::indexOf(const PlaylistEntry* e) const
{
    for( int i = 0; i < _entries.count(); ++i )
    {
        if ( e == _entries[i] )
            return index(i,0, QModelIndex());
    }

    return QModelIndex();
}

QList<PlaylistEntry*> PlayList::allEntries() const
{
    return _entries;
}

bool PlayList::saveOnDisk(IPlaylistWriter* w)
{
    bool canSave = w != NULL || _writer != NULL;
    if ( !canSave )
        return false;

    if ( w != NULL )
    {
        delete _writer;
        _writer = w;
    }

    return _writer->save(_entries);
}

void PlayList::setSortOrder(Qt::SortOrder order)
{
    _sortOrder = order;
}

Qt::SortOrder PlayList::sortOrder() const
{
    return _sortOrder;
}

void PlayList::setPlayMode(PlaybackModes mode)
{
    _playMode = mode;
}

PlaybackModes PlayList::playMode() const
{
    return _playMode;
}

void PlayList::sortByFilename()
{
    if ( _entries.count() == 0 )
        return;

    if ( _entries.count() < 200 )
        qSort(_entries.begin(), _entries.end(), _sortOrder == Qt::AscendingOrder ? util::fileNameLessThan : util::fileNameGreaterThan);
    else
        qStableSort(_entries.begin(), _entries.end(), _sortOrder == Qt::AscendingOrder ? util::fileNameLessThan : util::fileNameGreaterThan);
}

void PlayList::sortByTitle()
{
    if ( _entries.count() == 0 )
        return;

    if ( _entries.count() < 200 )
        qSort(_entries.begin(), _entries.end(), _sortOrder == Qt::AscendingOrder ? util::titleLessThan : util::titleGreaterThan);
    else
        qStableSort(_entries.begin(), _entries.end(), _sortOrder == Qt::AscendingOrder ? util::titleLessThan : util::titleGreaterThan);
}

void PlayList::shuffle()
{
    if ( _entries.count() == 0 )
        return;

    int n = _entries.count();
    int k = 0;
    while(n > 1)
    {
        k = util::randrange(n);  // 0..n-1
        n = n - 1;
        _entries.swap(k, n);
    }
}

void PlayList::clear()
{
    if ( _entries.count() == 0 )
        return;

    beginRemoveRows(QModelIndex(), 0, _entries.count()-1);

    for ( int i = 0; i < _entries.count(); ++i )
    {
        PlaylistEntry *e = _entries.at(i);
        delete e;
        e = NULL;
    }
    _entries.clear();

    endRemoveRows();
}

quint32 PlayList::length() const
{
    return _entries.count();
}

QModelIndex PlayList::search(const QString& search, QList<QModelIndex>& matches,
                             const QModelIndex & start) const
{
    int istart = 0;
    if ( start.isValid() )
        istart = start.row();
    for ( int i = istart; i < _entries.length(); ++i )
    {
        PlaylistEntry* e = _entries[i];
        if ( !e )
            continue;

        if (e->toString().contains(search, Qt::CaseInsensitive))
            matches.append(index(i, 0));
    }

    if ( istart > 0 )
    {
        for ( int i = istart; i >= 0; i-- )
        {
            PlaylistEntry* e = _entries[i];
            if ( !e )
                continue;
            if (e->toString().contains(search, Qt::CaseInsensitive))
                matches.append(index(i, 0));
        }
    }

    if ( matches.size() )
        return matches[0];
    return QModelIndex();
}
